CloudFormationを使用してECS Fargateを構築してみた
CloudFormationでECS Fargateを構築する機会があったのでブログに残します。
やること
タイトルの通りCloudFormationを使用してECS Fargateを動かせる環境を作成します。
ECS Fargateで使用するコンテナイメージはCloudShellで作成してECRへpushします。
ECRへのアクセスなどはVPCエンドポイントを使用する構成とします。
簡単にはなりますが構成図は以下となります。
AWSリソースの作成
まずはECSを動かすネットワーク周りのリソースとECRを作成します。
作成は以下のCloudFormationテンプレートで行いました。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09" Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# VPCCIDR: Default: 10.0.0.0/16 Type: String PublicSubnet01CIDR: Default: 10.0.0.0/24 Type: String PublicSubnet02CIDR: Default: 10.0.1.0/24 Type: String PrivateSubnet01CIDR: Default: 10.0.2.0/24 Type: String PrivateSubnet02CIDR: Default: 10.0.3.0/24 Type: String Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: ecs-test-vpc # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# PublicSubnet01: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a CidrBlock: !Ref PublicSubnet01CIDR Tags: - Key: Name Value: ecs-test-public-01 VpcId: !Ref VPC PublicSubnet02: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1c CidrBlock: !Ref PublicSubnet02CIDR Tags: - Key: Name Value: ecs-test-public-02 VpcId: !Ref VPC PrivateSubnet01: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a CidrBlock: !Ref PrivateSubnet01CIDR Tags: - Key: Name Value: ecs-test-private-01 VpcId: !Ref VPC PrivateSubnet02: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1c CidrBlock: !Ref PrivateSubnet02CIDR Tags: - Key: Name Value: ecs-test-private-02 VpcId: !Ref VPC # ------------------------------------------------------------# # InternetGateway # ------------------------------------------------------------# InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ecs-test-igw InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: ecs-test-public-rtb PublicRouteTableRoute: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway RouteTableId: !Ref PublicRouteTable PublicRtAssociation1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet01 PublicRtAssociation2: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet02 PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: ecs-test-private-rtb PrivateRtAssociation1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnet01 PrivateRtAssociation2: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnet02 # ------------------------------------------------------------# # Security Group # ------------------------------------------------------------# ALBSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for alb GroupName: ecs-test-sg-alb SecurityGroupIngress: - FromPort: 80 IpProtocol: tcp CidrIp: 0.0.0.0/0 ToPort: 80 Tags: - Key: Name Value: ecs-test-sg-alb VpcId: !Ref VPC ECSSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for ecs GroupName: test-sg-ecs SecurityGroupIngress: - FromPort: 80 IpProtocol: tcp SourceSecurityGroupId: !Ref ALBSG ToPort: 80 Tags: - Key: Name Value: ecs-test-sg-ecs VpcId: !Ref VPC VPCEndpointSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for VPC Endpoint GroupName: ecs-test-vpc-endpoint-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - SourceSecurityGroupId: !Ref ECSSG FromPort: 443 IpProtocol: tcp ToPort: 443 Tags: - Key: Name Value: ecs-test-vpc-endpoint-sg VpcId: !Ref VPC # ------------------------------------------------------------# # VPC Endpoint # ------------------------------------------------------------# S3Endpoint: Type: AWS::EC2::VPCEndpoint Properties: RouteTableIds: - !Ref PrivateRouteTable ServiceName: !Sub com.amazonaws.${AWS::Region}.s3 VpcEndpointType: Gateway VpcId: !Ref VPC ECRdkrEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG ECRapiEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG LogsEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.logs VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG SsmMessagesEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG # ------------------------------------------------------------# # ALB # ------------------------------------------------------------# ALB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 Name: alb Scheme: internet-facing SecurityGroups: - !Ref ALBSG Subnets: - !Ref PublicSubnet01 - !Ref PublicSubnet02 Tags: - Key: Name Value: ecs-test-alb Type: application TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckEnabled: true HealthCheckIntervalSeconds: 30 HealthCheckPath: / HealthCheckPort: 80 HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 5 IpAddressType: ipv4 Matcher: HttpCode: 200 Name: ecs-test-tg Port: 80 Protocol: HTTP ProtocolVersion: HTTP1 Tags: - Key: Name Value: ecs-test-tg TargetType: ip UnhealthyThresholdCount: 2 VpcId: !Ref VPC ALBHTTPListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref ALB Port: 80 Protocol: HTTP # ------------------------------------------------------------# # ECR # ------------------------------------------------------------# ECR: Type: AWS::ECR::Repository Properties: EmptyOnDelete: true EncryptionConfiguration: EncryptionType: AES256 RepositoryName: ecs-test-ecr
VPCやサブネット周りの詳細な説明は省かせていただきますが、31行目から201行目でVPC、サブネット、インターネットゲートウェイ、ルートテーブル、セキュリティグループを作成しています。
206行目~249行目でVPCエンドポイントを作成しています。
作成しているのはECRのVPCエンドポイント、S3のVPCエンドポイント、CloudWatch LogsのVPCエンドポイント、SystemsManagerのVPCエンドポイントとなります。
ECRにアクセスするのに最低限必要なVPCエンドポイントは以下のドキュメントに記載されています。
Amazon ECR インターフェイス VPC エンドポイント (AWS PrivateLink)
254行目~302行目でタスクを紐づけるALBを作成しています。
307行目~313行目でECRの作成を行っています。
デプロイは以下のコマンドを実行します。
VPCやサブネットのCIDRを変更したい場合は「--parameters」オプションでパラメータを指定してください。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名
デプロイが完了したらCloudShellへアクセスします。
アクセス方法は以下のドキュメントをご確認ください。
AWS CloudShell の使用を開始するには?
アクセスができたら、ECS Fargateで動かすコンテナイメージを作成していきます。
まずは以下のDockerfileを作成してください。
内容はシンプルでApacheのコンテナイメージを作成するものとなっています。
FROM httpd:2.4 RUN echo "ecs-test" > /usr/local/apache2/htdocs/index.html
Dockerfileを作成したら以下のコマンドでコンテナイメージを作成します。
「docker images」を実行すると作成されたイメージが確認できます。
docker build -t ecs-test . docker images
イメージが作成できたら以下のコマンドでタグを付けます。
docker tag ecs-test:latest AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-ecr:latest
タグの設定後、以下のコマンドでECRに対して認証を行いコンテナイメージをpushします。
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com docker push AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-ecr:latest
pushが成功するとECRの画面から以下のようにイメージが確認できます。
ECRへのpushまで完了したらECSを作成していきます。
ECSは以下のCloudFormationテンプレートで作成しました。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09" Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# TargetGroupArn: Type: String SecurityGroupID: Type: String PrivateSubnet01ID: Type: String PrivateSubnet02ID: Type: String Resources: # ------------------------------------------------------------# # CloudWatch Logs # ------------------------------------------------------------# ECSLogGroup: Type: "AWS::Logs::LogGroup" Properties: LogGroupName: !Sub "/ecs/logs/ecs-test-log" # ------------------------------------------------------------# # IAM # ------------------------------------------------------------# TaskExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ecs-tasks.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy RoleName: ecs-test-tast-execution-role TaskRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ecs-tasks.amazonaws.com Action: - sts:AssumeRole RoleName: ecs-test-tast-role TaskRolePolicy: Type: AWS::IAM::Policy Properties: PolicyName: ecs-test-task-role-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "ssmmessages:CreateControlChannel" - "ssmmessages:CreateDataChannel" - "ssmmessages:OpenControlChannel" - "ssmmessages:OpenDataChannel" Resource: '*' Roles: - !Ref TaskRole # ------------------------------------------------------------# # ECS # ------------------------------------------------------------# ECSCluster: Type: AWS::ECS::Cluster Properties: CapacityProviders: - FARGATE ClusterName: ecs-test-cluster DefaultCapacityProviderStrategy: - CapacityProvider: FARGATE Weight: 1 ECSTaskDef: Type: AWS::ECS::TaskDefinition Properties: ContainerDefinitions: - Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/ecs-test-ecr:latest LogConfiguration: LogDriver: awslogs Options: awslogs-group: "/ecs/logs/ecs-test-log" awslogs-region: !Ref "AWS::Region" awslogs-stream-prefix: "ecs-test-log" Name: ecs-test-task PortMappings: - ContainerPort: 80 HostPort: 80 Cpu: 256 ExecutionRoleArn: !Ref TaskExecutionRole Family: ecs-test-task-def Memory: 512 NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: !Ref TaskRole ECSService: Type: AWS::ECS::Service Properties: Cluster: !Ref ECSCluster DesiredCount: 1 EnableExecuteCommand: true LoadBalancers: - ContainerName: ecs-test-task ContainerPort: 80 TargetGroupArn: !Ref TargetGroupArn NetworkConfiguration: AwsvpcConfiguration: SecurityGroups: - !Ref SecurityGroupID Subnets: - !Ref PrivateSubnet01ID - !Ref PrivateSubnet02ID ServiceName: ecs-test-service TaskDefinition: !Ref ECSTaskDef
上記のCloudFormationテンプレートではCloudWatch LogsロググループやECSの使用するタスクロール、タスク実行ロールの作成とECS周りのリソース作成を行っています。
47行目~76行目でECSのタスクロールを作成しています。
このタスクロールではECS Execでコンテナ接続に必要なIAMポリシーを設定するようにしています。
ECS Execは120行目の「EnableExecuteCommand」というもので有効化しています。
今回はタスクが起動できるところまでを確認したかったのでApplication AutoScalingの設定は入れていません。
サービスのオートスケーリング
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=TargetGroupArn,ParameterValue=ECSタスクを紐づけるターゲットグループのARN ParameterKey=SecurityGroupID,ParameterValue=ECSタスクが使用するセキュリティグループのID ParameterKey=PrivateSubnet01ID,ParameterValue=1つ目のプライベートサブネットのID ParameterKey=PrivateSubnet02ID,ParameterValue=2つ目のプライベートサブネットのID --capabilities CAPABILITY_NAMED_IAM
デプロイ完了後、ALBのDNS名にブラウザからアクセスすると「ecs-test」という文字列が表示されることを確認できます。
また、ECSクラスターの画面からタスクが1つ起動していることが確認できます。
タスクにECS Execで接続するには以下のコマンドを実行します。
aws ecs execute-command --cluster ecs-test-cluster --task タスクのID --container ecs-test-task --interactive --command "/bin/sh"
さいごに
今回はApplication AutoScalingの設定までは入れませんでしたが必要であれば以下のドキュメントのリソースを使用して作成することが可能です。
Application Auto Scaling resource type reference